package HLA1.control;

import HLA1.entity.Job;
import HLA1.entity.CentroServizio;
import HLA1.generatori.GeneratoreUniforme;
import HLA1.generatori.GeneratoreIperEsponenziale;
import HLA1.federate.*;
import java.util.*;


public class Simulatore {
	
    /*
    SCPU = BCPU/CCPU = 1795/45600 = 0.0394 sec.
    S Disk1 = B Disk1/C Disk1 = 400/32000 = 0.0125 sec.
    S Disk2 = B Disk2/C Disk2 = 600/12000 = 0.05 sec.
    */

    public double media_cpu = 0.0394;
    public double media_disk1 = 0.0125;
    public double media_disk2 = 0.05;
    public Vector<Job> completati = new Vector<Job>();
    public ArrayList<Job> job1 = new ArrayList<Job>();
    public ArrayList<Job> job2 = new ArrayList<Job>();
    private GeneratoreUniforme g = new GeneratoreUniforme(12573);
    private GeneratoreUniforme routing = new GeneratoreUniforme(g.generaSeme());
    public CentroServizio cpu = new CentroServizio(new GeneratoreIperEsponenziale(0.3,media_cpu,g.generaSeme(),g.generaSeme()));
	public Federate1 simulazione;
    private int count_coda_disk1 = 0;
    private int count_coda_disk2 = 0;

    private int punto_stabilizzazione;
	private double somma_vettore = 0.0;
	private int job = 0;
	private double x_segnato = 0.0;
	private int step = 1;

	//tempi attesa in coda
	private Vector<Double> tempi_attesa = new Vector<Double>();
	private Vector<Double> valori_iid_coda = new Vector<Double>();
	//tempi risposta sistema
	private Vector<Double> tempi_risposta = new Vector<Double>();
	private Vector<Double> valori_iid_risposta = new Vector<Double>();
	private int cc = 25;

    //Variabile per gestire la fine della simulazione
    public boolean fine = true;

    /*
	 * Parametri del calendario eventi
	 */
	private int numero_sorgenti;
	private Double tempo_totale = 0.0;
	private Vector<Job> calendarioEventi = new Vector<Job>();
	private int job_entranti = 0;
	private int job_completati = 0;

    /*
	 * Costruttore
	 * Prende come parametro il numero di sorgenti
	 */
	public Simulatore(int sorgenti, Federate1 simulazione){
		this.numero_sorgenti = sorgenti;
		this.inizializzaCalendario();

		switch(sorgenti){ //inizializzo il valore di stabilizzazione studiato
		case 5:
			this.punto_stabilizzazione = 1200;
			break;
		case 10:
			this.punto_stabilizzazione = 1250;
			break;
		case 15:
			this.punto_stabilizzazione = 1300;
			break;
		case 20:
			this.punto_stabilizzazione = 1400;
			break;
		case 30:
			this.punto_stabilizzazione = 1500;
			break;
		default:
			this.punto_stabilizzazione = 1330;
			break;
		}

        this.simulazione = simulazione;

	}
	
	
	/*
	 * CALENDARIO EVENTI
	 */
		/*
		 * Inizializziamo il calendario eventi. Il calendario e' rappresentato da un
		 * vettore di tempi da 0 ad n-1 dove n e' la somma del numero di sorgenti, dela cpu,
		 * del disco1 e del disco2. I primi tempi, quelli dei terminali vengono inizializzati
		 * ad un tempo dato dal generatore Esponenziale, i tempi dei centri invece ad un tempo
		 * grande.
		 */
         
        private void inizializzaCalendario(){

			for(int i =0; i<this.numero_sorgenti;i++){
				double next=  ((GeneratoreIperEsponenziale)(cpu.getGen())).getGenE().next();  //this.genEsponenziale();
				calendarioEventi.add(new Job(next)); //inserisco nel calendario eventi un evento con t_in = next

			}

			for(int i=this.numero_sorgenti; i<(this.numero_sorgenti+3); i++){
				calendarioEventi.add(new Job(999999.99));
			}

		}

        /*
         * OSSERVATORI PER IL CALCOLO DEI RISULTATI
    	 */

		public void stabilizzatore(){
			//prendo l'ultimo job
			Job ev = this.completati.lastElement();
				//prendo il tempo di risposta e calcolo le x segnate dentro il bound definit
					this.somma_vettore += ev.getTempoRisposta();
					this.job +=1;
					this.x_segnato = this.somma_vettore/job;
			if(this.cc==25){
			System.out.println(this.x_segnato);
			this.cc=0;
			}else{
				this.cc++;
			}
		}

       /*
        * SEQUENZIATORE
        * Ci occupiamo ora di sequenziare gli eventi in base ai valori del calendario
        */
		public void sequenziatore(){
			int indice_tempo_minimo = this.getMinIndex();

			if ( indice_tempo_minimo < (calendarioEventi.size() - 3 ) ){
	            //evento in arrivo alla cpu
				this.job_entranti++;
                gestioneCPU x = new gestioneCPU(this);
                x.arrivoCpu();
			}

			if ( indice_tempo_minimo == (calendarioEventi.size() - 3 )  ){
			    //evento fine cpu
				gestioneCPU x = new gestioneCPU(this);
                x.fineCpu();
			}

			if ( indice_tempo_minimo == (calendarioEventi.size() - 2 ) ){
			    //evento fine disco 1
				gestioneDischi gest_disco1 = new gestioneDischi(this);
                gest_disco1.disco1();
			}

			if ( indice_tempo_minimo == (calendarioEventi.size() - 1 ) ){
			   //evento fine disco 2
                gestioneDischi gest_disco2 = new gestioneDischi(this);
                gest_disco2.disco2();
			}
			this.tempo_totale = this.getMinTime();
		}


		public int batchTempiAttesaCoda(int job){
			if(this.step>50){
				return 1;
			}

			int lower_bound = (this.step*this.punto_stabilizzazione);
			int upper_bound = ((this.step+1)*this.punto_stabilizzazione);

            if(job>=this.punto_stabilizzazione){
				Job ev = this.completati.lastElement();
				if(job>=lower_bound && job<=upper_bound){
					tempi_attesa.add(ev.getTempoCodaDisco());
				}
				if(job==upper_bound){
					int indice = (int)this.punto_stabilizzazione/2;
					double valore_iid = tempi_attesa.get(indice);
					valori_iid_coda.add(valore_iid);
					tempi_attesa.clear();
					System.out.println(" -> Preso valore iid per tw_coda nel batch "+this.step+" ("+valore_iid+")");
					this.step++;
				}
			}

			return 1;
		}

		public int batchTempiRisposta(int job){
			if(this.step>50){
				return 1;
			}

			int lower_bound = (this.step*this.punto_stabilizzazione);
			int upper_bound = ((this.step+1)*this.punto_stabilizzazione);
			if(job>=this.punto_stabilizzazione){
				Job ev = this.completati.lastElement();
				if(job>=lower_bound && job<=upper_bound){
					tempi_risposta.add(ev.getTempoRisposta());
				}
				if(job==upper_bound){
					int indice = (int)this.punto_stabilizzazione/2;
					double valore_iid = tempi_risposta.get(indice);
					valori_iid_risposta.add(valore_iid);
					tempi_risposta.clear();
					System.out.println("Preso valore iid per tr nel batch "+this.step+" ("+valore_iid+")");
				}
			}

			return 1;
		}

		/*
		 * getMinTime
		 * prende il tempo minimo all'interno del calendario eventi,
		 * serve a decidere qual e' il prossimo evento da considerare.
		 * Consideriamo infatti quello con tempo minimo
		 */
		public double getMinTime(){
			double value = 9999999.99;

			for(int i = 0; i<calendarioEventi.size(); i++){
				if(calendarioEventi.get(i).getTempoAttuale()<value){
					value = calendarioEventi.get(i).getTempoAttuale();
				}
			}
			return value;
		}

		/*
		 * getMinIndex
		 * prende l'indice del tempo minimo all'interno del calendario eventi,
		 * serve a decidere qual e' il prossimo evento da considerare.
		 * Consideriamo infatti quello con tempo minimo
		 */
		public int getMinIndex(){
			int index = 9999999;
			double value = 9999999.99;
			for(int i = 0; i<calendarioEventi.size(); i++){
				if(calendarioEventi.get(i).getTempoAttuale()<value){
					index = i;
					value = calendarioEventi.get(i).getTempoAttuale();
				}
			}

			return index;
		}

		/*
		 * getTempoCalendario
		 * Restituisce il valore del calendario eventi per l'evento alla posizione index
		 */
		public double getTempoCalendario(int index){
			return calendarioEventi.get(index).getTempoAttuale();
		}

		/*
		 * printCalendarioEventi
		 * Utilizzata sopratutto per debug, ci permette di visualizzare
		 * l'evolversi del calendario eventi durante la simulazione.
		 */
		public void printCalendarioEventi(){
			for(int i=0;i<this.calendarioEventi.size();i++){
				System.out.println("Indice "+i+" - Valore = "+this.calendarioEventi.get(i).getTempoAttuale());
			}
			System.out.println("------------------------------");

		}


        /*
		 * restituzioni di valori, usati in fase di debug.
		 */
		public double getTempoTotale(){
			return this.tempo_totale;
		}

		public int getJobEntranti(){
			return this.job_entranti;
		}

		public int getJobCompletati(){
			return this.job_completati;
		}

    

		public int getDimCompletati(){
			return this.completati.size();
		}

		public Job getLastJob(){
			return this.completati.lastElement();
		}

		public double getXsegnato(){
			return this.x_segnato;
		}

        public GeneratoreUniforme getG()
        {
            return g;
        }
      

        public void set_JobCompletati()
        {
            this.job_completati++;
        }

        public Vector<Job> getCalendarioEventi()
        {
            return calendarioEventi;
        }


		public void printValoriIidCoda(){
			for(int i=0; i<valori_iid_coda.size(); i++){
				System.out.println(valori_iid_coda.get(i));
			}
		}

		public void printValoriIidRisposta(){
			for(int i=0; i<valori_iid_risposta.size(); i++){
				System.out.println(valori_iid_risposta.get(i));
			}
		}

		private double tempo_medio_risposta;
		private double tempo_medio_coda_disco;

		public void printCompletati(){
			Job ev;
			for(int i = 0; i<this.completati.size();i++ ){
				ev = this.completati.get(i);
				System.out.println("Tin "+ev.getTin()+" - Attuale "+ev.getTempoAttuale()+" - Trisp "+ev.getTempoRisposta()+ " - Tempo in coda disco "+ev.getTempoCodaDisco());
				this.tempo_medio_risposta = (this.tempo_medio_risposta+ev.getTempoRisposta());
				if(ev.getTempoCodaDisco()>0){
					this.tempo_medio_coda_disco +=ev.getTempoCodaDisco();
				}
			}
			this.tempo_medio_risposta = (this.tempo_medio_risposta/this.completati.size());
			this.tempo_medio_coda_disco = (this.tempo_medio_coda_disco/this.completati.size());
		}

		public void printTempiDisco(){
			Job ev;
			for(int i = 0; i<this.completati.size();i++ ){
				ev = this.completati.get(i);
				System.out.println("TinCoda "+ev.getIngressoCoda()+ " - TinDisco "+ev.getIngressoDisco());
			}
		}

}